package com.piesat.quake.common.mongo.service.impl;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.mongodb.client.result.UpdateResult;
import com.piesat.quake.common.mongo.service.MongoService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Service;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

@Slf4j
@Service
@RequiredArgsConstructor
public class MongoServiceImpl implements MongoService {

    private final MongoTemplate mongoTemplate;

    @Override
    public <T> T save(T t) {
        mongoTemplate.save(t);
        return t;
    }

    @Override
    public <T> void deleteById(T t) {
        mongoTemplate.remove(t);
    }

    @Override
    public <T> void removeAll(Query query, Class<T> entityClass) {
        mongoTemplate.findAllAndRemove(query, entityClass);
    }

    @Override
    public <T> UpdateResult update(Query query, Update update, Class<T> entityClass) {
        return mongoTemplate.updateMulti(query, update, entityClass);
    }

    @Override
    public <T> UpdateResult updateFirst(Query query, Update update, Class<T> entityClass) {
        return mongoTemplate.updateFirst(query, update, entityClass);
    }

    @Override
    public <T> UpdateResult upsert(Query query, Update update, Class<T> entityClass) {
        return mongoTemplate.upsert(query, update, entityClass);
    }

    @Override
    public <T> Update buildBaseUpdate(T t) {
        Update update = new Update();
        Field[] fields = t.getClass().getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            try {
                Object value = field.get(t);
                if (value != null) {
                    update.set(field.getName(), value);
                }
            } catch (IllegalAccessException e) {
                log.error("异常信息：{}", e.getMessage());
            }
        }
        return update;
    }

    @Override
    public <T> UpdateResult updateById(String id, T t, Class<T> entityClass) {
        Query query = new Query();
        query.addCriteria(Criteria.where("_id").is(id));
        Update update = this.buildBaseUpdate(t);
        return update(query, update, entityClass);
    }

    @Override
    public <T> UpdateResult updateById(Integer id, T t, Class<T> entityClass) {
        return this.updateById(String.valueOf(id), t, entityClass);
    }

    @Override
    public <T> UpdateResult updateById(Integer id, Update update, Class<T> entityClass) {
        Query query = new Query(Criteria.where("_id").is(String.valueOf(id)));
        return this.updateFirst(query, update, entityClass);
    }

    @Override
    public <T> List<T> find(Query query, Class<T> entityClass) {
        return mongoTemplate.find(query, entityClass);
    }

    @Override
    public <T> T findById(Integer id, Class<T> entityClass) {
        return this.findById(String.valueOf(id), entityClass);
    }

    @Override
    public <T> T findById(String id, Class<T> entityClass) {
        return mongoTemplate.findById(id, entityClass);
    }

    @Override
    public <T> List<T> findByIdIn(Iterable<Integer> ids, Class<T> entityClass) {
        if (ids != null) {
            List<String> strIds = new ArrayList<>();
            ids.forEach(id -> strIds.add(String.valueOf(id)));
            if (CollectionUtils.isNotEmpty(strIds)) {
                return mongoTemplate.find(new Query(Criteria.where("_id").in(strIds)), entityClass);
            }
        }
        return Collections.emptyList();
    }

    @Override
    public <T> T findOne(Query query, Class<T> entityClass) {
        return mongoTemplate.findOne(query, entityClass);
    }

    @Override
    public <T> List<T> find(Query query, Class<T> entityClass, String... excludeFields) {
        setExcludeFields(query, excludeFields);
        return find(query, entityClass);
    }

    @Override
    public <T> T findOne(Query query, Class<T> entityClass, String... excludeFields) {
        setExcludeFields(query, excludeFields);
        return findOne(query, entityClass);
    }

    @Override
    public <T> long count(Query query, Class<T> entityClass) {
        return mongoTemplate.count(query, entityClass);
    }

    @Override
    public <T> Page<T> pageInfo(int currentPage, int pageSize, Query query, Class<T> entityClass) {
        if (currentPage == 0) {
            currentPage = 1;
        }
        if (pageSize == 0) {
            pageSize = 10;
        }
        long count = this.count(query, entityClass);
        long totalPage = count % pageSize > 0 ? count / pageSize + 1 : count / pageSize;
        int skip = (currentPage - 1) * pageSize;
        List<T> list = this.find(query.skip(skip).limit(pageSize), entityClass);
        Page<T> page = new Page<>();
        page.setTotal(count);
        page.setRecords(list);
        return page;
    }

    @Override
    public <T> AggregationResults<T> aggregate(Aggregation aggregation, Class<?> inputType, Class<T> outputType) {
        return mongoTemplate.aggregate(aggregation, inputType, outputType);
    }

    @Override
    public <T> void insertAll(List<T> list) {
        mongoTemplate.insertAll(list);
    }

    @Override
    public <T> UpdateResult updateMulti(Query query, Update update, Class<T> entityClass) {
        return mongoTemplate.updateMulti(query, update, entityClass);
    }

    /**
     * 排除MongoDB查询返回的一些字段
     *
     * @param query
     * @param excludeFields
     */
    private void setExcludeFields(Query query, String... excludeFields) {
        if (null != query && null != excludeFields) {
            for (String field : excludeFields) {
                query.fields().exclude(field);
            }
        }
    }
}
